`timescale 1ns / 1ps
/******************************************************************************
*                                                                             *
* UTICA softcore v0.1                                                         *
*                                                                             *
* Copyright (c) 2012 Andrew D. Zonenberg                                      *
* All rights reserved.                                                        *
*                                                                             *
* Redistribution and use in source and binary forms, with or without modifi-  *
* cation, are permitted provided that the following conditions are met:       *
*                                                                             *
*    * Redistributions of source code must retain the above copyright notice  *
*      this list of conditions and the following disclaimer.                  *
*                                                                             *
*    * Redistributions in binary form must reproduce the above copyright      *
*      notice, this list of conditions and the following disclaimer in the    *
*      documentation and/or other materials provided with the distribution.   *
*                                                                             *
*    * Neither the name of the author nor the names of any contributors may be*
*      used to endorse or promote products derived from this software without *
*      specific prior written permission.                                     *
*                                                                             *
* THIS SOFTWARE IS PROVIDED BY THE AUTHORS "AS IS" AND ANY EXPRESS OR IMPLIED *
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF        *
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN     *
* NO EVENT SHALL THE AUTHORS BE HELD LIABLE FOR ANY DIRECT, INDIRECT,         *
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT    *
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,   *
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY       *
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT         *
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF    *
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.           *
*                                                                             *
******************************************************************************/

/**
	@file SpiNorFlashController.v
	@author Andrew D. Zonenberg
	@brief NOR flash controller with native (not memory mapped) interface.
	
	Core logic runs at up to 80 MHz but the transceiver runs at Fclk / 2 since SI on the target board
	currrently limits us to 40 MHz. Note that the W25Q80BV has a maximum operating frequency of 50-104
	MHz depending on the specific instruction executing.
	
	Note that it is possible to issue a read burst of up to 255 bytes, larger than the 32-byte
	capacity of the FIFO. If this is done, 	the parent module MUST pop the FIFO as data arrives
	to prevent overflow.
	
	No caching.
 */
module SpiNorFlashController(
	clk, busy,
	spi_sck, spi_cs_n, spi_data,
	cmd_en, cmd_din, cmd_addr, cmd_burstlen,
	read_en, read_data, read_overflow, read_underflow, read_full, read_empty
    );
	 
	////////////////////////////////////////////////////////////////////////////////////////////////
	// IO declarations
	
	//Global control flags
	input wire clk;
	output reg busy = 0;
		
	//SPI bus
	output wire spi_sck;
	output reg spi_cs_n = 1;
	inout wire[3:0] spi_data;

	//Command signals
	localparam CMD_NOP				= 4'b0000;
	localparam CMD_READ_JEDEC_ID	= 4'b0001;
	localparam CMD_READ_UUID		= 4'b0010;
	localparam CMD_READ_DATA		= 4'b0011;
	//localparam CMD_QUAD_ENABLE		= 4'b0100;
	
	//Interface to memory-mapping logic
	localparam CMD_WIDTH = 4;
	input wire cmd_en;							//assert to send us a command
	input wire[CMD_WIDTH-1:0] cmd_din;
	input wire[23:0] cmd_addr;
	input wire[7:0] cmd_burstlen;
	
	//Read data bus
	input wire read_en;
	output wire[7:0] read_data;
	output wire read_overflow;
	output wire read_underflow;
	output wire read_full;
	output wire read_empty;
	
	////////////////////////////////////////////////////////////////////////////////////////////////
	// Read data FIFO
	wire spi_done;
	wire[7:0] rx_data;
	reg read_active = 0;
	SmallLutramFifo #(.WIDTH(8)) read_data_fifo (
		.clk(clk), 
		.wr(spi_done && read_active), 
		.din(rx_data), 
		.rd(read_en), 
		//.dout_c(dout_c), 	//combinatorial output not used
		.dout_r(read_data), 
		.overflow(read_overflow), 
		.underflow(read_underflow), 
		.empty(read_empty), 
		.full(read_full)
		);
		
	////////////////////////////////////////////////////////////////////////////////////////////////
	// SPI transceiver
	wire spi_busy;
	reg tx_en_single = 0;
	reg tx_en_quad = 0;
	reg[7:0] tx_data = 0;
	reg rx_en_single = 0;
	reg rx_en_quad = 0;
	QuadSpiTransceiver transceiver (
		.clk(clk), 
		.busy(spi_busy), 
		.done(spi_done), 
		.spi_sck(spi_sck), 
		.spi_data(spi_data), 
		.tx_en_single(tx_en_single), 
		.tx_en_quad(tx_en_quad), 
		.tx_data(tx_data), 
		.rx_en_single(rx_en_single), 
		.rx_en_quad(rx_en_quad), 
		.rx_data(rx_data)
		);

	////////////////////////////////////////////////////////////////////////////////////////////////
	// State machine logic for flash controller
	
	reg[CMD_WIDTH-1:0] active_cmd = 0;
	reg[3:0] cmd_stage = 0;
	reg[7:0] bytecount = 0;
	
	reg[23:0] saved_addr = 0;
	reg[7:0] maxbytecount = 0;	//burst length
	
	always @(posedge clk) begin
	
		//Reset control logic
		tx_en_single <= 0;
		tx_en_quad <= 0;
		tx_data <= 0;
		rx_en_single <= 0;
		rx_en_quad <= 0;
		
		//Keep track of whether a read is in progress
		if(spi_done)
			read_active <= 0;
		if(rx_en_single || rx_en_quad)
			read_active <= 1;
		
		//Continue doing whatever we're doing, no new commands can be accepted
		if(busy) begin
			
			//State machine logic for commands
			case(active_cmd)
				
				CMD_READ_JEDEC_ID: begin
				
					case(cmd_stage)
						//Send read-ID command (0x9F) in single mode
						0: begin
							tx_en_single <= 1;
							tx_data <= 8'h9f;
							cmd_stage <= 1;
							bytecount <= 0;
						end
						
						//Wait for tx / rx to finish, then read the ID bytes back (three in total)
						1: begin
							if(spi_done) begin
															
								//Done reading the third byte? We're finished
								if(bytecount == 3) begin
									cmd_stage <= 0;
									busy <= 0;
									active_cmd <= CMD_NOP;
									spi_cs_n <= 1;
								end
								
								//No, read the next byte
								else begin
									rx_en_single <= 1;
									bytecount <= bytecount + 8'd1;
								end
								
							end
						end
						
					endcase
					
				end	//CMD_READ_JEDEC_ID
				
				CMD_READ_UUID: begin
					case(cmd_stage)
						//Send read-ID command (0x4B) in single mode
						0: begin
							tx_en_single <= 1;
							tx_data <= 8'h4B;
							cmd_stage <= 1;
							bytecount <= 0;
						end
						
						//Send four dummy bytes
						1: begin
							if(spi_done) begin

								//Done writing the fourth byte? Time to read
								if(bytecount == 4) begin
									cmd_stage <= 2;
									rx_en_single <= 1;
									bytecount <= 1;
								end
														
								//No, send the next dummy byte
								else begin
									tx_en_single <= 1;
									tx_data <= 8'h00;
									bytecount <= bytecount + 8'd1;
								end						
							
							end
						end
						
						//Read eight bytes of data
						2: begin
						
							if(spi_done) begin
															
								//Done reading the eighth byte? We're finished
								if(bytecount == 8) begin
									cmd_stage <= 0;
									busy <= 0;
									active_cmd <= CMD_NOP;
									spi_cs_n <= 1;
								end
								
								//No, read the next byte
								else begin
									rx_en_single <= 1;
									bytecount <= bytecount + 8'd1;
								end
								
							end
						
						end
					
					endcase
					
				end	//CMD_READ_UUID
				
				CMD_READ_DATA: begin
					case(cmd_stage)
						
						//Send read-data command (0x03) in single mode
						0: begin
							tx_en_single <= 1;
							tx_data <= 8'h03;
							cmd_stage <= 1;
							bytecount <= 0;
						end
						
						//Send the address
						1: begin
							if(spi_done) begin
							
								//Done writing the third address byte? Time to read
								if(bytecount == 3) begin
									cmd_stage <= 2;
									rx_en_single <= 1;
									bytecount <= 1;
								end
														
								//No, send the next address byte and shift the address left
								else begin
									tx_en_single <= 1;
									tx_data <= saved_addr[23:16];
									saved_addr <= {saved_addr[15:0], 8'h00};
									bytecount <= bytecount + 8'd1;
								end						
							
							end
						end
						
						//Read the data
						2: begin
						
							if(spi_done) begin
															
								//Done reading the last byte? We're finished
								if(bytecount == maxbytecount) begin
									cmd_stage <= 0;
									busy <= 0;
									active_cmd <= CMD_NOP;
									spi_cs_n <= 1;
								end
								
								//No, read the next byte
								else begin
									rx_en_single <= 1;
									bytecount <= bytecount + 8'd1;
								end
								
							end
						
						end
						
					endcase
				end	//CMD_READ_DATA
				
			endcase
			
		end
		
		//New command passed to us
		else if(cmd_en) begin
			case(cmd_din)
				CMD_NOP: begin
					//nothing
				end
				
				//Read JEDEC ID code
				CMD_READ_JEDEC_ID: begin
					busy <= 1;
					active_cmd <= CMD_READ_JEDEC_ID;
					cmd_stage <= 0;
					spi_cs_n <= 0;					
				end
				
				//Read 64-bit serial number
				CMD_READ_UUID: begin
					busy <= 1;
					active_cmd <= CMD_READ_UUID;
					cmd_stage <= 0;
					spi_cs_n <= 0;		
				end
				
				//Read address
				CMD_READ_DATA: begin
					busy <= 1;
					active_cmd <= CMD_READ_DATA;
					cmd_stage <= 0;
					spi_cs_n <= 0;	
					saved_addr <= cmd_addr;
					maxbytecount <= cmd_burstlen;
				end
				
			endcase
		end
		
		//Debug - check for collisions
		if(cmd_en && busy) begin
			$display("[SpiNorFlashController] Warning - command issued while busy, ignoring it (t=%d ns)",
				$time());
		end
		
	end

endmodule
